package edu.cornell.cubooks.dao.impl;

import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

import edu.cornell.cubooks.CUBooksConstants;
import edu.cornell.cubooks.dao.UserDao;
import edu.cornell.cubooks.domain.Role;
import edu.cornell.cubooks.domain.User;

/**
 * Implementation of the UserDao and UserDetailsService interfaces.
 * 
 * @author CUBooks team
 */
@Transactional
public class UserDaoImpl extends GenericDaoImpl<User, Integer> implements UserDao, UserDetailsService {
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	private Integer maxLoginAttempts = 3;
	
	public UserDaoImpl(Integer maxLoginAttempts) {
		this.maxLoginAttempts = maxLoginAttempts;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#userNameExists(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public boolean userNameExists(String username) {
		User user = (User) sessionFactory.getCurrentSession()
			.createCriteria(domainClass)
			.add( Restrictions.eq(CUBooksConstants.USERNAME, username))
			.uniqueResult();
		if (user == null) {
			return false;
		} 
		return true;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#userNameExists(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public boolean userNameExistsForOtherUser(String username, int id) {
		User user = (User) sessionFactory.getCurrentSession()
			.createCriteria(domainClass)
			.add(Restrictions.ne("id", id))
			.add(Restrictions.eq(CUBooksConstants.USERNAME, username))
			.uniqueResult();
		if (user == null) {
			return false;
		} 
		return true;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		User user = (User) sessionFactory.getCurrentSession()
			.createCriteria(domainClass)
			.add( Restrictions.eq(CUBooksConstants.USERNAME, username))
			.uniqueResult();
		return user;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#sendResetPasswordEmail(edu.cornell.cubooks.domain.User)
	 */
	@Transactional(readOnly = false)
	public String createToken(User user) {
		String token =  getPasswordEncoder().encodePassword(user.getEmailAddress(), null);
		user.setToken(token);
		this.update(user);
		return token;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#checkToken(edu.cornell.cubooks.domain.User, java.lang.String)
	 */
	public boolean checkToken(User user, String token) {
		if (user.getToken().equals(token)) {
			return true;
		}
		return false;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.impl.GenericDaoImpl#save(java.io.Serializable)
	 */
	@Transactional(readOnly = false)
	public User save(User user) {
		//authenticate(user.getUsername(), user.getPassword());
		String encPassword =  getPasswordEncoder().encodePassword(user.getPassword(), null); 
		user.setPassword(encPassword);
		//clear cpassword
		user.setCpassword(null);
		
		return (User) sessionFactory.getCurrentSession().merge(user);
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#loadUserByEmail(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public User loadUserByEmail(String email) {
		User user = (User) sessionFactory.getCurrentSession()
		.createCriteria(domainClass)
		.add( Restrictions.eq(CUBooksConstants.EMAIL_ADDRESS, email))
		.uniqueResult();
		return user;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#updateFailLoginAttepmts(java.lang.String)
	 */
	@Transactional(readOnly = false)
	public void updateFailLoginAttepmts(String username) {
		User user = (User) loadUserByUsername(username);
		user.setLoginAttempts(user.getLoginAttempts() + 1);
		if (user.getLoginAttempts().compareTo(maxLoginAttempts) >= 0) {
			//lock account
			user.setAccountNonLocked(false);
			//TODO: send email to reset password
			update(user);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#changePassword(edu.cornell.cubooks.domain.User)
	 */
	@Transactional(readOnly = false)
	public void changePassword(User user) {
		String encPassword =  getPasswordEncoder().encodePassword(user.getPassword(), null); 
		user.setPassword(encPassword);
		user.setAccountNonLocked(true);
		user.setLoginAttempts(0);
		user.setToken("");
		user.setCpassword("");
		this.update(user);
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#emailExists(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public boolean emailExists(String email) {
		User user = (User) sessionFactory.getCurrentSession()
			.createCriteria(domainClass)
			.add(Restrictions.eq("emailAddress", email))
			.uniqueResult();
		if (user == null) {
			return false;
		} 
		return true;
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#emailExistsForOtherUser(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public boolean emailExistsForOtherUser(String email, int id) {
		User user = (User) sessionFactory.getCurrentSession()
			.createCriteria(domainClass)
			.add(Restrictions.ne("id", id))
			.add(Restrictions.eq("emailAddress", email))
			.uniqueResult();
		if (user == null) {
			return false;
		} 
		return true;
	}
	
	public void updateWithPasswordEncryption(User user) {
		changePassword(user);
	}
	
	// getters and setters
	public PasswordEncoder getPasswordEncoder() {
		return passwordEncoder;
	}

	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	public Integer getMaxLoginAttempts() {
		return maxLoginAttempts;
	}

	public void setMaxLoginAttempts(Integer maxLoginAttempts) {
		this.maxLoginAttempts = maxLoginAttempts;
	}	
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#sendSellerAccountApprovedEmail(edu.cornell.cubooks.domain.User)
	 */
	@Transactional(readOnly = false)
	public void sendSellerAccountApprovedEmail(User user) {
		
	}
	
	/*
	 * (non-Javadoc)
	 * @see edu.cornell.cubooks.dao.UserDao#approveSeller(edu.cornell.cubooks.domain.User)
	 */
	public void approveSeller(User user) {
		Role role = new Role(Role.ROLE_SELLER);
		user.getRoles().add(role);
		user.setRequestSeller(0);
		update(user);
	}
}

